﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace Framework.Sql
{
    public class SqlExecutor
    {
        private readonly string _connectionString;

        public SqlExecutor(string connectionString)
        {
            _connectionString = connectionString;
        }

        public SqlParameterAdder WithCommand(string sql)
        {
            return new SqlParameterAdder(_connectionString, sql);
        }
        
        public void Exec(string sql)
        {
            using (var connection = new SqlConnection(_connectionString))
            {
                connection.Open();
                using (var command = new SqlCommand(sql, connection))
                    command.ExecuteNonQuery();
                connection.Close();
            }
        }

        private static IEnumerable<SqlDataReader> GetReaderAsEnumerable(SqlDataReader reader)
        {
            var shouldContinue = true;
            try
            {
                shouldContinue = reader.Read();
            }
            catch
            {
                reader.Close();
                reader.Dispose();
                throw;
            }

            while (shouldContinue)
            {
                yield return reader;
                try
                {
                    shouldContinue = reader.Read();
                }
                catch
                {
                    reader.Close();
                    reader.Dispose();
                    throw;
                }
            }
        }

        public IEnumerable<T> Exec<T>(string sql, Func<IDataRecord, T> fn)
        {
            using (var connection = new SqlConnection(_connectionString))
            {
                connection.Open();
                using (var command = new SqlCommand(sql, connection))
                {
                    SqlDataReader reader = null;
                    try
                    {
                        reader = command.ExecuteReader();
                    }
                    catch (Exception e)
                    {
                        if (reader != null && !reader.IsClosed) reader.Close();
                        if (reader != null) reader.Dispose();

                        throw new Exception("Sql failed: " + sql, e);
                    }

                    foreach (var r in GetReaderAsEnumerable(reader))
                    {
                        T result;
                        try
                        {
                            result = fn(r);
                        }
                        catch
                        {
                            reader.Close();
                            reader.Dispose();
                            throw;
                        }
                        yield return result;
                    }
                    reader.Close();
                    reader.Dispose();
                }
                connection.Close();
            }
        }

        public void Exec(string sql, Action<IDataRecord> fn)
        {
            Exec(sql, reader => { fn(reader); return true; });
        }
    }
}